
package org.puremvc.as3.multicore.utilities.assetLoader.model
{

	import flash.events.*;
	import flash.utils.Dictionary;
	
	import org.puremvc.as3.multicore.utilities.assetLoader.clients.LoaderClientBase;
	import org.puremvc.as3.multicore.utilities.assetLoader.etypes.LoaderNotificationNames;

	import org.puremvc.as3.multicore.utilities.assetLoader.events.LoaderErrorEvent;
	import org.puremvc.as3.multicore.utilities.assetLoader.events.LoaderEvent;
	import org.puremvc.as3.multicore.utilities.assetLoader.interfaces.*;
	
	public class Loader extends EventDispatcher
	{
		private var mDataMap:Dictionary;
		private var mClientMap:Object;
		private var mLoadQue:Array;
		private var mIsLoading:Boolean;
		private var mIsAbortLoading:Boolean;
		private var mIsAutoLoad:Boolean;
		private var mIsProrityChanged:Boolean;
		private var mCount:int;
		private var mNumInQue:int;
		
		private var mCurrentItemBytesLoaded:Number;
        private var mCurrentItemBytesTotal:Number;

        private var mQueLoadProgress:Number;

		private var mQueSort:Function;
		
		
		/**
		 * the constructor
		 */
		public function Loader() 
		{
			init();
		}
		
		/**
		 * indicates whether the load que is active
		 */
		public function get isActive():Boolean{
			return mIsLoading;
		}
		
		/**
		 * the number of items left in the load que
		 */
		public function get queLength():int{
			return mLoadQue.length;
		}
		

		
		/**
		 * the function that defines the load que order
		 */
		public function set queSortFunction(param:Function):void
		{
			mQueSort = param;
			mIsProrityChanged = true;
		}
		
		/**
		 * defines whether the load que should automatically start when an item has been added
		 */
		public function get autoLoad():Boolean
		{
			return mIsAutoLoad;
		}
		
		/**
		 * @private
		 */
		public function set autoLoad(param:Boolean):void
		{
			mIsAutoLoad = param;
		}
		
		/**
		 * the load que
		 */
		private function get loadQue():Array{
			return mLoadQue;
		}
		
		public function getAsset(url:String):IAssetInfo
		{
			if (mDataMap[url] == null)
			{
				return new LoadRequest(url);
			}else
			{
				return mDataMap[url]
			}
		}
		
		public function hasLoaded(url:String):Boolean
		{
			if (mDataMap[url] == null)
			{
				return false;
			}else
			{
				return (mDataMap[url] as IAssetInfo).hasLoaded;
			}
		}
		
		public function deleteAsset(url:String):void
		{
			if (mDataMap[url])
			{
				LoadRequest(mDataMap[url]).deconstruct();
				delete mDataMap[url];
			}
		}
		
		public function addToQue(param:IAssetInfo):void{
			
			var request:LoadRequest;
			
			request = LoadRequest(param)
			
			if(request.clientType == null) throw new Error("the clientType must be set")
			
			loadQue.push(request);
			mNumInQue++;
			
			mIsProrityChanged = true;
			
			if(!mIsLoading && mIsAutoLoad)
			{
				startQue();		
			}
		}
		
		public function stopQue():void{
			
			mIsAbortLoading = true;
			
		}
		public function startQue():void{
			
			mIsLoading = true;
			loadNext();
		}
		
		//TODO: impliment Loader deconstruct method
		public function deconstruct():void
		{
		
		}
		
		public function registerClient(id:String, c:Class):void
		{
			mClientMap[id] = c;
		}
		
		public function removeClient(id:String):void
		{
			if(mClientMap[id])
			{
				delete mClientMap[id];
			}
		}
		
		public function getClient(id:String):Class{
			
			if (mClientMap[id] == null)
			{
				throw new Error("no client registered with id: "+id)
			}
			
			return Class(mClientMap[id])
		}
		
		private function init():void
		{
			mDataMap = new Dictionary();
			mClientMap = new Object();
			mLoadQue = new Array();
			mIsAutoLoad = true;
		}
		
		
		
		private function loadNext():void
		{			
			var request:LoadRequest,
				clazz:Class,
				client:LoaderClientBase,
				e:LoaderEvent;
			
			if(mIsProrityChanged)sortQue();
						
			request = LoadRequest (loadQue.shift() );
			
			addListeners(request)
			
			clazz = getClient(request.clientType);
			client = new clazz(request);
			client.load();
			
			e = new LoaderEvent(	LoaderNotificationNames.ITEM_STARTED,
									mCurrentItemBytesLoaded,
									mCurrentItemBytesTotal,
									request.url,
									mNumInQue,
									mNumInQue - mCount,
									mQueLoadProgress			);
			dispatchEvent(e)
			
		}
		
		private function sortQue():void
		{
			if (mQueSort != null)loadQue.sort(mQueSort);
			mIsProrityChanged = false;
		}
		
		
		private function ioErrorHandler(event:IOErrorEvent):void
		{
			var e:LoaderErrorEvent,
				request:LoadRequest;	
			
			request = LoadRequest(event.target);
			removeListeners(request);
			mNumInQue--;
			mCount--;
			
			e = new LoaderErrorEvent(	event.type, 
										event.text, 
										request.url	);
			dispatchEvent(e);
			endLoadCycle(request);  
			
		}
		
		private function securityErrorHandler(event:SecurityErrorEvent):void
		{
			var e:LoaderErrorEvent,
				request:LoadRequest;	
			
			request = LoadRequest(event.target);
			removeListeners(request);
			mNumInQue--;
			mCount--;
			
			e = new LoaderErrorEvent(	event.type, 
										event.text, 
										request.url	);
			dispatchEvent(e);
			endLoadCycle(request);  
			
		}
		
		
		
		private function loadRequestCompleteHandler(request:LoadRequest):void
		{
			var e:LoaderEvent;	
			
			mCount++;		
			
			removeListeners(request)
			addRequestToMap(request);
			
			e = new LoaderEvent(	LoaderNotificationNames.ITEM_COMPLETED,
									mCurrentItemBytesLoaded,
									mCurrentItemBytesTotal,
									request.url,
									mNumInQue,
									mNumInQue - mCount,
									mQueLoadProgress			);
										
			dispatchEvent(e);
			endLoadCycle(request);

		}
		
		private function addListeners(request:LoadRequest):void
		{
			
			request.loaderCallBack = clientCallBackHandler;
			
			request.addEventListener(ProgressEvent.PROGRESS, loadRequestProgressHandler);
			request.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler)
			request.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler)
			request.addEventListener(Event.CHANGE, priorityChangeHandler)
			
		}
		
		private function removeListeners(request:LoadRequest):void
		{
			request.loaderCallBack = null;
		}
		
		private function priorityChangeHandler(event:Event):void
		{
			mIsProrityChanged = true;
			
		}
		
		// yerch!
		private function clientCallBackHandler(request:LoadRequest):void
		{
			
			loadRequestCompleteHandler(request);
				
		}
		
		
		
		
		
		private function addRequestToMap(request:LoadRequest):void
		{
			if (request.store)
			{
				mDataMap[request.url] = request;
			}
			
		}
		
		private function endLoadCycle(request:LoadRequest):void
		{
			
			var e:LoaderEvent
			
			
			
			if(loadQue.length > 0 || mIsAbortLoading)
			{
				loadNext();	
				
			}else
			{
				
				mIsLoading = false;
				mIsAbortLoading = false;
				

				
				e = new LoaderEvent(	LoaderNotificationNames.QUE_COMPLETED,
										mCurrentItemBytesLoaded,
										mCurrentItemBytesTotal,
										request.url,
										mNumInQue,
										mNumInQue - mCount,
										mQueLoadProgress			);
											
				dispatchEvent(e)
				
				resetVars()
				
			}
		}
		
		private function resetVars():void
		{
			mCount = 0;
			mNumInQue = 0;
			mCurrentItemBytesLoaded = 0;
        	mCurrentItemBytesTotal = 0;


        	mQueLoadProgress = 0;
		}
		
		private function loadRequestProgressHandler(event:ProgressEvent):void
		{
			var e:LoaderEvent,
				request:LoadRequest;
				
			request = LoadRequest(event.target);
			// this nice equation from QueueLoader @ hydrotik - thanks
			mQueLoadProgress = (((mCount * (100 / (mNumInQue))) + ((event.bytesLoaded / event.bytesTotal) * (100 / (mNumInQue)))) * .01);
			
			
			mCurrentItemBytesLoaded = event.bytesLoaded;
			mCurrentItemBytesTotal = event.bytesTotal;
			
			
			e = new LoaderEvent(	LoaderNotificationNames.QUE_PROGRESS,
									mCurrentItemBytesLoaded,
									mCurrentItemBytesTotal,
									request.url,
									mNumInQue,
									mNumInQue - mCount,
									mQueLoadProgress			);
											
			dispatchEvent(e)
			
		}
		
		
		

	}
	
}



	
	
	/**
	* ...
	* @author DefaultUser (Tools -> Custom Arguments...)
	*/
	
	
	
	
	
	